實踐物件的 Deep Copy,避免 Shallow Copy 的問題以及省去重新製作物件。
Prototype 源自於物件的複製問題,如果資料的型別是字串、數字、布林等,可以直接複製(call by value),物件與陣列這兩類只會複製記憶體的儲存位置(call by reference),這將導致複製不完全(Shallow Copy 或 Shallow Clone),新舊物件共用相同的物件與陣列,肯定會出事。
因此,為了完全複製(Deep Copy 或 Deep Clone),可以採用 Prototype,作法是:
Abstract、Interface、Class。Prototype:NewTasteBeverage
public class NewTasteBeverage implements Cloneable {
    private String baseBeverage;
    private String taste;
    private String size;
    private IdInfo idInfo;
    public NewTasteBeverage(String baseBeverage) {
        this.baseBeverage = baseBeverage;
        idInfo = new IdInfo();
    }
    /**
     * Deep Clone 需要注意的點
     */
    private NewTasteBeverage(IdInfo idInfo) throws CloneNotSupportedException {
        this.idInfo = (IdInfo) idInfo.clone();
    }
    public void setProduceInfo(String location, String factoryAddress) {
        idInfo.setLocation(location);
        idInfo.setFactoryAddress(factoryAddress);
    }
    public void setSeriesNumber(String seriesNumber) {
        idInfo.setSeriesNumber(seriesNumber);
    }
    public void setTaste(String taste) {
        this.taste = taste;
    }
    public void setSize(String size) {
        this.size = size;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        NewTasteBeverage clonedObject = new NewTasteBeverage(this.idInfo);
        clonedObject.baseBeverage = this.baseBeverage;
        clonedObject.taste = this.taste;
        clonedObject.size = this.size;
        return clonedObject;
    }
    public void showInfo() {
        System.out.println("產品是:" + taste + " " + baseBeverage + " " + size);
        System.out.println("產地資訊:" + idInfo.getFactoryAddress() + " " + idInfo.getLocation());
        System.out.println("序號:" + idInfo.getSeriesNumber() + "\n");
    }
}
class IdInfo implements Cloneable {
    private String seriesNumber;
    private String location;
    private String factoryAddress;
    public String getSeriesNumber() {
        return seriesNumber;
    }
    public void setSeriesNumber(String seriesNumber) {
        this.seriesNumber = seriesNumber;
    }
    public String getLocation() {
        return location;
    }
    public void setLocation(String location) {
        this.location = location;
    }
    public String getFactoryAddress() {
        return factoryAddress;
    }
    public void setFactoryAddress(String factoryAddress) {
        this.factoryAddress = factoryAddress;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
進行複製
public class PrototypeSample {
    public static void main(String[] args) throws CloneNotSupportedException {
        System.out.println("進行第一項產品的嘗試");
        NewTasteBeverage beverage1 = new NewTasteBeverage("可樂");
        beverage1.setSize("350毫升");
        beverage1.setTaste("香草");
        beverage1.setProduceInfo("桃園", "臺灣");
        beverage1.setSeriesNumber("3617263");
        System.out.println("更換,進行第二項");
        NewTasteBeverage beverage2 = (NewTasteBeverage) beverage1.clone();
        beverage2.setTaste("檸檬");
        beverage2.setSeriesNumber("8911733");
        System.out.println("更換,進行最後一項");
        NewTasteBeverage beverage3 = (NewTasteBeverage) beverage1.clone();
        beverage3.setProduceInfo("臺中", "臺灣");
        beverage3.setSeriesNumber("1657209");
        beverage1.showInfo();
        beverage2.showInfo();
        beverage3.showInfo();
    }
}
Prototype:NewTasteBeverage
class NewTasteBeverage {
  constructor(baseBeverage) {
    this.baseBeverage = baseBeverage;
    this.taste = '';
    this.size = '';
    this.idInfo = new IdInfo();
  }
  setProduceInfo(location, factoryAddress) {
    this.idInfo.setLocation(location);
    this.idInfo.setFactoryAddress(factoryAddress);
  }
  setSeriesNumber(seriesNumber) {
    this.idInfo.setSeriesNumber(seriesNumber);
  }
  setTaste(taste) {
    this.taste = taste;
  }
  setSize(size) {
    this.size = size;
  }
  clone() {
    const clonedObject = Object.create(this);
    clonedObject.idInfo = this.idInfo.clone();
    clonedObject.baseBeverage = this.baseBeverage;
    clonedObject.taste = this.taste;
    clonedObject.size = this.size;
    return clonedObject;
  }
  showInfo() {
    console.log("產品是:" + this.taste + " " + this.baseBeverage + " " + this.size);
    console.log("產地資訊:" + this.idInfo.getFactoryAddress() + " " + this.idInfo.getLocation());
    console.log("序號:" + this.idInfo.getSeriesNumber() + "\n");
  }
}
class IdInfo {
  constructor() {
    this.seriesNumber = '';
    this.location = '';
    this.factoryAddress = '';
  }
  getSeriesNumber() {
    return this.seriesNumber;
  }
  setSeriesNumber(seriesNumber) {
    this.seriesNumber = seriesNumber;
  }
  getLocation() {
    return this.location;
  }
  setLocation(location) {
    this.location = location;
  }
  getFactoryAddress() {
    return this.factoryAddress;
  }
  setFactoryAddress(factoryAddress) {
    this.factoryAddress = factoryAddress;
  }
  clone() {
    const clonedObject = Object.create(this);
    clonedObject.seriesNumber = this.seriesNumber;
    clonedObject.location = this.location;
    clonedObject.factoryAddress = this.factoryAddress;
    return clonedObject;
  }
}
進行複製
const prototypeSample = () => {
  console.log("進行第一項產品的嘗試");
  const beverage1 = new NewTasteBeverage("可樂");
  beverage1.setSize("350毫升");
  beverage1.setTaste("香草");
  beverage1.setProduceInfo("桃園", "臺灣");
  beverage1.setSeriesNumber("3617263");
  console.log("更換,進行第二項");
  const beverage2 = beverage1.clone();
  beverage2.setTaste("檸檬");
  beverage2.setSeriesNumber("8911733");
  console.log("更換,進行最後一項");
  const beverage3 = beverage1.clone();
  beverage3.setProduceInfo("臺中", "臺灣");
  beverage3.setSeriesNumber("1657209");
  beverage1.showInfo();
  beverage2.showInfo();
  beverage3.showInfo();
}
prototypeSample();
過往的開發 JavaScript 的經驗,處理物件的 Deep Clone 時,最常使用的是 JSON.parse(JSON.stringify(obj)),這個做法的前提物件本身的組成只有 JSON 允許的字串、數字、布林值、物件、陣列,其他像是 undefined、Symbol、Set、Map 會因為 JSON 不支援導致複製後得到的是 {}。除此之外,物件之間的繼承也會變重置,因為 JSON.parse() 會建立新物件,不可能會知道 JSON 前的繼承關係。
藉由這個模式,可以暸解實作 Deep Clone 是所有物件導向語言的都會遇到的問題,趁著這次的學習與了解,讓自己往後遇到擁有複雜繼承關係的物件時,多了一種處理方式。
明天將介紹最後一個 Creational patterns: Prototype 模式。